package com.laiyuezs.sdk2x;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.protos.common.MspPrincipal;
import org.hyperledger.fabric.protos.common.Policies;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.*;

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.fabric.sdk.Channel.DiscoveryOptions.createDiscoveryOptions;

/**
 * 关于 chaincode 的所有功能
 *
 * @author : 州长在手 2021/1/14 上午10:58
 *
 * 2021/7/8 下午14:49修改, 优化了私有数据和普通数据的invoke
 */
@Slf4j
public class Cc {

    /**
     * 单个提案请求的超时时间以毫秒为单位
     */
    private static final int proposalWaitTime = 100000;

    /**
     * 私有数据使用的inovke
     * @param client
     * @param channel
     * @param chaincodeName
     * @param fcn
     * @param transientMap
     * @return
     * @throws InvalidArgumentException
     * @throws ServiceDiscoveryException
     * @throws ProposalException
     * @throws InterruptedException
     * @throws ExecutionException
     * @throws TimeoutException
     * @throws UnsupportedEncodingException
     */
    public static JSONObject invoke(HFClient client, Channel channel, String chaincodeName, String fcn, Map<String, byte[]> transientMap) throws InvalidArgumentException, ServiceDiscoveryException, ProposalException, InterruptedException, ExecutionException, TimeoutException, UnsupportedEncodingException {
        TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();
        transactionProposalRequest.setChaincodeID(ChaincodeID.newBuilder().setName(chaincodeName).build());
        transactionProposalRequest.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);
        transactionProposalRequest.setUserContext(client.getUserContext());
        transactionProposalRequest.setFcn(fcn);
        transactionProposalRequest.setProposalWaitTime(proposalWaitTime);
        transactionProposalRequest.setTransientMap(transientMap);
        Channel.TransactionOptions opts = new Channel.TransactionOptions();
        Collection<ProposalResponse> transactionPropResp = channel.sendTransactionProposal(transactionProposalRequest, channel.getPeers());
        JSONObject result = new JSONObject();
        transactionPropResp.forEach(response -> {
            try {
                result.put("load", new String(response.getChaincodeActionResponsePayload()));
            } catch (InvalidArgumentException e) {
                log.error(response.getMessage());

            }
        });
        return channel.sendTransaction(transactionPropResp, opts)
                .thenApply(transactionEvent ->
                {
                    result.put("code", InUtils.SUCCESS);
                    result.put("time", transactionEvent.getTimestamp());
                    result.put("txid", transactionEvent.getTransactionID());
                    result.put("height", transactionEvent.getBlockEvent().getBlockNumber());
                    return result;
                }).get();
    }

    public static JSONObject invoke(HFClient client, Channel channel, String chaincodeName, String fcn, String... args) throws InvalidArgumentException, ServiceDiscoveryException, ProposalException, InterruptedException, ExecutionException, TimeoutException {
        TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();

        transactionProposalRequest.setChaincodeID(ChaincodeID.newBuilder().setName(chaincodeName).build());
        transactionProposalRequest.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);
        transactionProposalRequest.setUserContext(client.getUserContext());
        transactionProposalRequest.setFcn(fcn);
        transactionProposalRequest.setProposalWaitTime(proposalWaitTime);
        transactionProposalRequest.setArgs(args);
        Channel.TransactionOptions opts = new Channel.TransactionOptions();
        Collection<ProposalResponse> transactionPropResp = channel.sendTransactionProposal(transactionProposalRequest, channel.getPeers());
        JSONObject result = new JSONObject();
        transactionPropResp.forEach(response -> {
            try {
                result.put("load", new String(response.getChaincodeActionResponsePayload()));
            } catch (InvalidArgumentException e) {
                log.error(response.getMessage());

            }
        });
        return channel.sendTransaction(transactionPropResp, opts)
                .thenApply(transactionEvent ->
                {
                    result.put("code", InUtils.SUCCESS);
                    result.put("time", transactionEvent.getTimestamp());
                    result.put("txid", transactionEvent.getTransactionID());
                    result.put("height", transactionEvent.getBlockEvent().getBlockNumber());
                    return result;
                }).get();
    }

    /**
     * 查询智能合约
     */
    public static JSONObject query(HFClient client, Channel channel, String chaincodeName, String fcn, String... args) throws InvalidArgumentException, ProposalException {

        QueryByChaincodeRequest queryByChaincodeRequest = client.newQueryProposalRequest();
        queryByChaincodeRequest.setArgs(args);
        queryByChaincodeRequest.setFcn(fcn);
        queryByChaincodeRequest.setChaincodeID(ChaincodeID.newBuilder().setName(chaincodeName).build());
        queryByChaincodeRequest.setChaincodeName(chaincodeName);
        Collection<ProposalResponse> queryProposals = channel.queryByChaincode(queryByChaincodeRequest, channel.getPeers());
        return toPeerResponse(queryProposals, true);

    }

    /**
     * 获取安装合约以及query合约的返回结果集合
     *
     * @param proposalResponses 请求返回集合
     * @param checkVerified     是否验证提案
     */
    private static JSONObject toPeerResponse(Collection<ProposalResponse> proposalResponses, boolean checkVerified) {
        JSONObject jsonObject = new JSONObject();
        for (ProposalResponse proposalResponse : proposalResponses) {
            if ((checkVerified && !proposalResponse.isVerified()) || proposalResponse.getStatus() != ProposalResponse.Status.SUCCESS) {
                String data = String.format("Failed install/query proposal from peer %s status: %s. Messages: %s. Was verified : %s",
                        proposalResponse.getPeer().getName(), proposalResponse.getStatus(), proposalResponse.getMessage(), proposalResponse.isVerified());
                jsonObject.put("code", InUtils.ERROR);
                jsonObject.put("error", data);
            } else {
                String payload = proposalResponse.getProposalResponse().getResponse().getPayload().toStringUtf8();
                proposalResponse.getProposalResponse().getResponse().getPayload();
                jsonObject = parseResult(payload);
                jsonObject.put("code", InUtils.SUCCESS);
                jsonObject.put("txid", proposalResponse.getTransactionID());
            }
        }
        return jsonObject;
    }


    private static JSONObject parseResult(String result) {
        JSONObject jsonObject = new JSONObject();
        int jsonVerify = isJSONValid(result);
        switch (jsonVerify) {
            case 0:
                jsonObject.put("data", result);
                break;
            case 1:
                jsonObject.put("data", JSONObject.parseObject(result));
                break;
            case 2:
                jsonObject.put("data", JSONObject.parseArray(result));
                break;
        }
        return jsonObject;
    }

    /**
     * 判断字符串类型
     *
     * @param str 字符串
     * @return 0-string；1-JsonObject；2、JsonArray
     */
    private static int isJSONValid(String str) {
        try {
            JSONObject.parseObject(str);
            return 1;
        } catch (JSONException ex) {
            try {
                JSONObject.parseArray(str);
                return 2;
            } catch (JSONException ex1) {
                return 0;
            }
        }
    }


    // -------------------------------------------------------------- parser yaml --------------------------------------------------------------
//    public static byte[] fromYamlFile(InputStream inputStream) {
//        try {
//            final Yaml yaml = new Yaml(new SafeConstructor());
//            final Map<?, ?> load = (Map<?, ?>) yaml.load(inputStream);
//
//            Map<?, ?> mp = (Map<?, ?>) load.get("policy");
//
//            if (null == mp) {
//                throw new ChaincodeEndorsementPolicyParseException("The policy file has no policy section");
//            }
//
//            IndexedHashMap<String, MspPrincipal.MSPPrincipal> identities = parseIdentities((Map<?, ?>) load.get("identities"));
//
//            Policies.SignaturePolicy sp = parsePolicy(identities, mp);
//
//            byte[] policyBytes = Policies.SignaturePolicyEnvelope.newBuilder()
//                    .setVersion(0)
//                    .addAllIdentities(identities.values())
//                    .setRule(sp)
//                    .build().toByteArray();
//
//            return policyBytes;
//        } catch (Exception e) {
//            log.error("解析 YAML 错误" + e.getMessage());
//            return null;
//        }
//    }
//
//    private static Policies.SignaturePolicy parsePolicy(IndexedHashMap<String, MspPrincipal.MSPPrincipal> identities, Map<?, ?> mp) throws ChaincodeEndorsementPolicyParseException {
//
//        if (mp == null) {
//            throw new ChaincodeEndorsementPolicyParseException("No policy section was found in the document.");
//        }
//        if (!(mp instanceof Map)) {
//            throw new ChaincodeEndorsementPolicyParseException("Policy expected object section was not found in the document.");
//
//        }
//
//        for (Map.Entry<?, ?> ks : mp.entrySet()) {
//            Object ko = ks.getKey();
//            Object vo = ks.getValue();
//            final String key = (String) ko;
//            // String val = (String) vo;
//
//            if ("signed-by".equals(key)) {
//
//                if (!(vo instanceof String)) {
//                    throw new ChaincodeEndorsementPolicyParseException("signed-by expecting a string value");
//                }
//
//                MspPrincipal.MSPPrincipal mspPrincipal = identities.get(vo);
//                if (null == mspPrincipal) {
//                    throw new ChaincodeEndorsementPolicyParseException(format("No identity found by name %s in signed-by.", vo));
//                }
//
//                return Policies.SignaturePolicy.newBuilder()
//                        .setSignedBy(identities.getKeysIndex((String) vo))
//                        .build();
//
//            } else {
//
//                Matcher match = Pattern.compile("^(\\d+)-of$").matcher(key);
//
//                if (match.matches() && match.groupCount() == 1) {
//
//                    String matchStingNo = match.group(1).trim();
//                    int matchNo = Integer.parseInt(matchStingNo);
//
//                    if (!(vo instanceof List)) {
//                        throw new ChaincodeEndorsementPolicyParseException(format("%s expected to have list but found %s.", key, String.valueOf(vo)));
//                    }
//
//                    @SuppressWarnings("unchecked") final List<Map<?, ?>> voList = (List<Map<?, ?>>) vo;
//
//                    if (voList.size() < matchNo) {
//
//                        throw new ChaincodeEndorsementPolicyParseException(format("%s expected to have at least %d items to match but only found %d.", key, matchNo, voList.size()));
//                    }
//
//                    Policies.SignaturePolicy.NOutOf.Builder spBuilder = Policies.SignaturePolicy.NOutOf.newBuilder()
//                            .setN(matchNo);
//
//                    for (Map<?, ?> nlo : voList) {
//
//                        Policies.SignaturePolicy sp = parsePolicy(identities, nlo);
//                        spBuilder.addRules(sp);
//
//                    }
//
//                    return Policies.SignaturePolicy.newBuilder().setNOutOf(spBuilder.build()).build();
//
//                } else {
//
//                    throw new ChaincodeEndorsementPolicyParseException(format("Unsupported policy type %s", key));
//                }
//
//            }
//
//        }
//        throw new ChaincodeEndorsementPolicyParseException("No values found for policy");
//
//    }
//
//
//    private static class IndexedHashMap<K, V> extends LinkedHashMap<K, V> {
//        final HashMap<K, Integer> kmap = new HashMap<>();
//
//        @Override
//        public V put(K key, V value) {
//            kmap.put(key, size());
//            return super.put(key, value);
//        }
//
//        Integer getKeysIndex(String n) {
//            return kmap.get(n);
//        }
//    }
//
//
//    private static IndexedHashMap<String, MspPrincipal.MSPPrincipal> parseIdentities(Map<?, ?> identities) throws ChaincodeEndorsementPolicyParseException {
//        //Only Role types are excepted at this time.
//
//        IndexedHashMap<String, MspPrincipal.MSPPrincipal> ret = new IndexedHashMap<>();
//
//        for (Map.Entry<?, ?> kp : identities.entrySet()) {
//            Object key = kp.getKey();
//            Object val = kp.getValue();
//
//            if (!(key instanceof String)) {
//                throw new ChaincodeEndorsementPolicyParseException(format("In identities key expected String got %s ", key == null ? "null" : key.getClass().getName()));
//            }
//
//            if (null != ret.get(key)) {
//                throw new ChaincodeEndorsementPolicyParseException(format("In identities with key %s is listed more than once ", key));
//            }
//
//            if (!(val instanceof Map)) {
//                throw new ChaincodeEndorsementPolicyParseException(format("In identities with key %s value expected Map got %s ", key, val == null ? "null" : val.getClass().getName()));
//            }
//
//            Object role = ((Map<?, ?>) val).get("role");
//
//            if (!(role instanceof Map)) {
//                throw new ChaincodeEndorsementPolicyParseException(format("In identities with key %s value expected Map for role got %s ", key, role == null ? "null" : role.getClass().getName()));
//            }
//            final Map<?, ?> roleMap = (Map<?, ?>) role;
//
//            Object nameObj = (roleMap).get("name");
//
//            if (!(nameObj instanceof String)) {
//                throw new ChaincodeEndorsementPolicyParseException(format("In identities with key %s name expected String in role got %s ",
//                        key, nameObj == null ? "null" : nameObj.getClass().getName()));
//            }
//            String name = (String) nameObj;
//            name = name.trim();
//            Object mspId = roleMap.get("mspId");
//
//            if (!(mspId instanceof String)) {
//                throw new ChaincodeEndorsementPolicyParseException(format("In identities with key %s mspId expected String in role got %s ", key, mspId == null ? "null" : mspId.getClass().getName()));
//            }
//
//            if (StringUtil.isNullOrEmpty((String) mspId)) {
//
//                throw new ChaincodeEndorsementPolicyParseException(format("In identities with key %s mspId must not be null or empty String in role ", key));
//
//            }
//
//            MspPrincipal.MSPRole.MSPRoleType mspRoleType;
//
//            switch (name) {
//                case "member":
//                    mspRoleType = MspPrincipal.MSPRole.MSPRoleType.MEMBER;
//                    break;
//                case "admin":
//                    mspRoleType = MspPrincipal.MSPRole.MSPRoleType.ADMIN;
//                    break;
//                case "client":
//                    mspRoleType = MspPrincipal.MSPRole.MSPRoleType.CLIENT;
//                    break;
//                case "peer":
//                    mspRoleType = MspPrincipal.MSPRole.MSPRoleType.PEER;
//                    break;
//                default:
//                    throw new ChaincodeEndorsementPolicyParseException(format("In identities with key %s name expected member, admin, client, or peer in role got %s ", key, name));
//            }
//
//            MspPrincipal.MSPRole mspRole = MspPrincipal.MSPRole.newBuilder().setRole(mspRoleType)
//                    .setMspIdentifier((String) mspId).build();
//
//            MspPrincipal.MSPPrincipal principal = MspPrincipal.MSPPrincipal.newBuilder()
//                    .setPrincipalClassification(MspPrincipal.MSPPrincipal.Classification.ROLE)
//                    .setPrincipal(mspRole.toByteString()).build();
//
//            ret.put((String) key, principal);
//
//        }
//
//        if (ret.size() == 0) {
//            throw new ChaincodeEndorsementPolicyParseException("No identities were found in the policy specification");
//        }
//
//        return ret;
//
//    }

}
